package base

import (
	"fmt"
	"gitee.com/ymofen/gobase"
	"gitee.com/ymofen/gobase/gojson"
	"gitee.com/ymofen/supervalue"
	"sync/atomic"
	"time"
)

type plugItem struct {
	id         string
	loadflag   int8
	confstr    string
	changedlst int8
	createidx  uint64
	uptime     time.Time
}

type PlgBaseLoader struct {
	createidx  uint64
	updateflag int32
	lstPlg     gobase.SyncMap
}

func NewPlgLoader() *PlgBaseLoader {
	return &PlgBaseLoader{}
}

func (this *PlgBaseLoader) checkGetPlgInfo(id string, newflag bool) *plugItem {
	if newflag {
		rec := this.lstPlg.LoadOrStoreFunc(id, func() interface{} {
			rval := &plugItem{id: id, createidx: atomic.AddUint64(&this.createidx, 1)}
			return rval
		}, nil).(*plugItem)
		return rec
	} else {
		rec := this.lstPlg.Get(id, (*plugItem)(nil)).(*plugItem)
		return rec
	}
}

func (this *PlgBaseLoader) GetPlugIdList() (lst []string) {
	this.lstPlg.Range(func(key, value interface{}) bool {
		itm := value.(*plugItem)
		lst = append(lst, itm.id)
		return true
	})
	return lst
}

func (this *PlgBaseLoader) GetPlugList(cb func(plg interface{}) bool) (lst []interface{}) {
	id := this.GetPlugIdList()
	for i := 0; i < len(id); i++ {
		plg := FindPluginByID(id[i])
		if plg != nil && cb(plg) {
			lst = append(lst, plg)
		}
	}
	return lst
}

func (this *PlgBaseLoader) checkClosePlug(all bool) (closeN int) {
	// 后创建的先移除
	this.lstPlg.ListRangeV2(nil, func(k0, v0, k1, v1 interface{}) bool {
		s0 := v0.(*plugItem)
		s1 := v1.(*plugItem)
		return s0.createidx > s1.createidx
	}, func(key, val interface{}) bool { // 优先移除
		plg := val.(*plugItem)
		if all || plg.loadflag == 0 {
			this.lstPlg.Remove(key)
			PlgLoadCloseConfEx(plg.id, plg.changedlst)
			closeN++
		} else {
			plg.loadflag = 0
		}
		return true
	})
	return
}

func (this *PlgBaseLoader) Close() error {
	this.checkClosePlug(true)
	return nil
}

func (this *PlgBaseLoader) BeginUpdate() {
	atomic.AddInt32(&this.updateflag, 1)
}

func (this *PlgBaseLoader) EndUpdate() (closeN int) {
	if atomic.AddInt32(&this.updateflag, -1) == 0 {
		return this.checkClosePlug(false)
	} else {
		return 0
	}
}

func (this *PlgBaseLoader) CloseAll() {
	this.checkClosePlug(true)
}

func (this *PlgBaseLoader) LoadPlugCloseConf(id string) bool {
	obj := this.lstPlg.Get(id, nil)
	if obj == nil {
		return false
	}
	plg := obj.(*plugItem)
	PlgLoadCloseConfEx(plg.id, plg.changedlst)
	this.lstPlg.Remove(id)
	return true
}

func (this *PlgBaseLoader) CheckLoadPlugin(id string, plugtype string, attrs ...interface{}) (r interface{}, err error) {
	conf := gojson.BuildJSONString(attrs...)
	return this.CheckLoadPluginConfig(id, plugtype, conf)
}

func (this *PlgBaseLoader) CheckLoadPluginConfig(id string, plugtype string, conf string) (r interface{}, err error) {
	if len(conf) == 0 {
		conf = "{}"
	}
	str := fmt.Sprintf(`{ "id": "%s",   "type": "%s", "conf": %s}`, id, plugtype, conf)
	this.ReloadJSONStr(str, func(e error) {
		err = e
	})
	if err != nil {
		return
	}
	r = FindPluginByID(id)
	return
}

func (this *PlgBaseLoader) ReloadJSONStr(s string, onerr func(err error)) (configN, loadN int) {
	json, err := wrapper.NewSVFromBuf([]byte(s))
	if err != nil && onerr != nil {
		onerr(fmt.Errorf("非法的插件配置格式 %s, \n%s", s, err.Error()))
	}
	if json != nil {
		return this.Reload(json, onerr)
	}
	return
}

func (this *PlgBaseLoader) DumpPlugsConfig() string {
	var bb gobase.BytesBuilder
	bb.AppendStr("[")
	i := 0
	// 后创建的先移除
	this.lstPlg.ListRangeV2(nil, func(k0, v0, k1, v1 interface{}) bool {
		s0 := v0.(*plugItem)
		s1 := v1.(*plugItem)
		return s0.createidx < s1.createidx
	}, func(key, val interface{}) bool { // 优先移除
		plg := val.(*plugItem)
		if i > 0 {
			bb.AppendStr(",\n")
		}
		bb.AppendStr(plg.confstr)
		i++
		return true
	})
	bb.AppendStr("]")
	return bb.String()
}

func (this *PlgBaseLoader) reload(plgTxt string, configN, loadN *int, onerr func(err error)) {
	plgTxt = PlgReplacePlaceholder(plgTxt)
	itmconf, err := wrapper.NewSVFromBufCache([]byte(plgTxt), true)
	if err != nil {
		onerr(fmt.Errorf("解析插件异常:%s\r\n%s", err.Error(), plgTxt))
	} else {
		defer itmconf.Clear()
		id := itmconf.StringByName("id", "")
		plgtype := itmconf.StringByName("type", "")
		if len(id) > 0 && len(plgtype) > 0 { // 有效的插件配置
			plg := this.checkGetPlgInfo(id, true)
			if plg.confstr != plgTxt || (plgtype != "NONE" && FindPluginByID(id) == nil) {
				plg.changedlst = itmconf.Int8ByPath("changedlst", 1)
				_, _, e1 := PlgCheckReloadFromJSON(itmconf)
				if e1 != nil {
					onerr(fmt.Errorf("加载插件异常:%s\r\n%s", e1.Error(), plgTxt))
				}
				plg.confstr = plgTxt
				plg.uptime = time.Now()
				*loadN++
			}
			if atomic.LoadInt32(&this.updateflag) == 1 {
				plg.loadflag = 1
			}
			*configN++
		}
		return
	}
}

func (this *PlgBaseLoader) Reload(json *wrapper.SuperValue, onerr func(err error)) (configN, loadN int) {
	var lst *wrapper.SuperValue
	if json.IsArray() {
		lst = json
	} else {
		lst = json.ValueByName("list")
		if lst == nil { // 单个插件
			if json.Int8ByName("__plug_disable", -1) == 1 {
				return
			}
			plgTxt := json.String()
			this.reload(plgTxt, &configN, &loadN, onerr)
			return
		}
	}

	//[{"id":"xxx"}]
	lst.Range(func(key string, rowval *wrapper.SuperValue) bool {
		// 插件被禁用
		if rowval.Int8ByName("__plug_disable", -1) == 1 {
			return true
		}
		plgTxt := rowval.String()
		this.reload(plgTxt, &configN, &loadN, onerr)
		return true
	})

	return
}

func (this *PlgBaseLoader) ReloadAll(json *wrapper.SuperValue, onerr func(err error)) (configN, loadN, closeN int) {
	this.BeginUpdate()
	defer func() {
		closeN = this.EndUpdate()
	}()

	configN, loadN = this.Reload(json, onerr)
	return
}
